﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Swift;
using Swift.Math;
using RVO;

namespace SCM
{
    /// <summary>
    /// 房间内的玩家数据
    /// </summary>
    public class PlayerInfoInRoom
    {
        // 各种资源数量
        public StableDictionary<string, Fix64> Resources = new StableDictionary<string, Fix64>();

        public Fix64 this[string resourceType]
        {
            get
            {
                if (!Resources.ContainsKey(resourceType))
                    Resources[resourceType] = 0;

                return Resources[resourceType];
            }
            set
            {
                Resources[resourceType] = value;
            }
        }
    }

    /// <summary>
    /// 一场战斗一个房间
    /// </summary>
    public class Room
    {
        #region 帧同步管理

        SRandom rand = null;
        public const int FrameInterval = 100; // 一帧逻辑帧的时间长度(毫秒)

        // 帧编号
        ulong frameSeqNo = 0;
        public ulong FrameNo { get { return frameSeqNo; } }

        // 随机种子
        public int RandomSeed { get { return rand.Seed; } }

        // 获取下一个随机数
        public int RandomNext(int min, int max) { return rand.Next(min, max); }

        // 获取下一个序列 ID
        protected string NextUniqueID { get { return ID + "_" + (++idSeq); } }
        int idSeq = 0;

        #endregion

        // 每个 Room 自己单独一个状态机管理器
        protected StateMachineManager smm = new StateMachineManager();

        // 房间 ID，全球唯一
        public string ID { get; protected set; }

        // 对战双方的 UseID
        public string[] UsrsID { get; protected set; }

        // 双方 UserInfo
        public UserInfo[] UsrsInfo { get; protected set; }

        // 双方的局内信息
        public PlayerInfoInRoom[] Players { get; protected set; }

        // 战斗地图
        protected Map Map { get; private set; }
        public Vec2 MapSize { get { return Map == null ? Vec2.Zero : Map.Size; } }

        #region 房间流程

        // 战斗是否结束
        public bool Started { get; private set; }
        public bool Finished { get; protected set; }
        public string Winner { get; protected set; }

        // 关卡信息
        public Level Lv { get; protected set; }
        public bool IsPVP { get { return Lv.IsPVP; } }

        public virtual void Init(string roomID, Vec2 mapSize, string lvID, string usr1, string usr2, UserInfo usrInfo1, UserInfo usrInfo2)
        {
            ID = roomID;

            var num = 3;

            UsrsID = new string[num];
            UsrsID[0] = null; // 0 表示中立单位
            UsrsID[1] = usr1;
            UsrsID[2] = usr2;

            UsrsInfo = new UserInfo[num];
            UsrsInfo[0] = UserInfo.CreateAllLevel1UserInfo();
            UsrsInfo[1] = usrInfo1 == null ? UserInfo.CreateAllLevel1UserInfo() : usrInfo1;
            UsrsInfo[2] = usrInfo2 == null ? UserInfo.CreateAllLevel1UserInfo() : usrInfo2; ;

            Players = new PlayerInfoInRoom[num];
            Players[0] = null;
            Players[1] = new PlayerInfoInRoom();
            Players[2] = new PlayerInfoInRoom();

            Lv = LevelCreator.GetLevel(lvID);

            BuffRunner = new BuffRunner();
            BuffRunner.Room = this;

            // if (IsPVP)
            {
                TBRunner = new TreasureBoxRunner();
                TBRunner.Room = this;
            }
            //else
            //    TBRunner = null;

            Map = new Map(mapSize, FrameInterval / 1000.0f);
            Map.Room = this;
            frameSeqNo = 0;

            smm.Clear();
        }

        public UnitConfigInfo GetUserUnitConfig(string usr, string unitType)
        {
            return GetPlayerUnitConfig(GetNoByUser(usr), unitType);
        }

        public UnitConfigInfo GetPlayerUnitConfig(int player, string unitType)
        {
            var info = UsrsInfo[player];
            return info == null ?
                UnitConfiguration.GetDefaultConfig(unitType) :
                UnitConfiguration.GetConfig(unitType, info.GetCardLv(unitType));
        }

        // 推进一步数据逻辑
        public const int MaxFrameNum = 9000; // 最多 15 分钟一局
        public virtual void MoveOneStep()
        {
            if (frameSeqNo >= MaxFrameNum)
            {
                BattleEnd(null);
                return;
            }

            frameSeqNo++;

            // 目标已死亡就从地图移除
            foreach (var u in AllUnits)
            {
                if (u.Hp <= 0)
                    RemoveUnit(u.UID);
            }

            smm.OnTimeElapsed(FrameInterval);

            // 降低物理模拟频率
            if (frameSeqNo % 2 == 0)
                Map.DoOneStep(FrameInterval * 2);

            // 宝箱和 buff

            if (TBRunner != null)
                TBRunner.OnTimeElapsed(FrameInterval);

            if (BuffRunner != null)
                BuffRunner.OnTimeElapsed(FrameInterval);
        }

        // 战斗开始
        public virtual void BattleBegin(int randomSeed)
        {
            rand = new SRandom(randomSeed);
            Started = true;
            Winner = null;
            Finished = false;
            frameSeqNo = 0;

            InitBattle();
        }

        // 初始化战斗内容
        protected virtual void InitBattle()
        {
            AddResource(1, "Money", 0);
            AddResource(2, "Money", 0);
            AddResource(1, "Suppliment", 0);
            AddResource(2, "Suppliment", 0);
            AddResource(1, "SupplimentLimit", 30);
            AddResource(2, "SupplimentLimit", 30);

            Lv.Init(this);
        }

        public UserInfo GetUserInfo(string uid)
        {
            if (uid == UsrsID[1])
                return UsrsInfo[1];
            else if (uid == UsrsID[2])
                return UsrsInfo[2];
            else
                return null;
        }

        // 检查结束条件，返回 winner player
        public virtual int CheckWinner()
        {
            return Lv.CheckWinner(this);
        }

        // 战斗结束
        public virtual void BattleEnd(string winner)
        {
            Winner = winner;
            Finished = true;
        }

        // 刷新指定单位的 AI
        public void RefreshAI(Unit u)
        {
            smm.Del(u.UID);
            var sm = u.CreateAI();
            if (sm != null)
                smm.Add(sm);
        }

        // 获取指定单位的 AI 状态机
        public StateMachine GetSM(Unit u)
        {
            return smm.Get(u.UID);
        }

        #endregion

        #region 房间内信息查询

        // 获取所有指定玩家指定类型的单位
        public Unit[] GetUnitsByType(string unitType, int player)
        {
            return FC.Select(AllUnits, (u) => u.Player == player && u.UnitType == unitType).ToArray();
        }

        // 获取指定位置的占位信息
        public int GetInGrid(int x, int y)
        {
            return Map.GetInGrid(x, y);
        }

        // 获取对应 user 的编号
        public int GetNoByUser(string usr)
        {
            return Array.IndexOf(UsrsID, usr);
        }

        // 获取指定用户的所有场景单位
        public Unit[] GetAllUnitsByUser(string usr, Func<Unit, bool> filter = null) { return GetAllUnitsByPlayer(GetNoByUser(usr), filter); }
        public Unit[] GetAllUnitsByPlayer(int player, Func<Unit, bool> filter = null)
        {
            var lst = new List<Unit>();
            foreach (var u in Map.AllUnits)
            {
                if (u.Player == player && (filter == null || filter(u)))
                    lst.Add(u);
            }

            return lst.ToArray();
        }

        // 是否存在满足指定条件的单位
        public bool ExistsUnit(Func<Unit, bool> filter)
        {
            foreach (var u in AllUnits)
            {
                if (filter(u))
                    return true;
            }

            return false;
        }

        // 获取指定 uid 的单位
        public Unit GetUnit(string uid)
        {
            return Map.Get(uid);
        }

        // 所有房间内单位
        public Unit[] AllUnits
        {
            get
            {
                return Map.AllUnits;
            }
        }

        // 获取指定资源数量
        public Fix64 GetResource(int p, string resourceType)
        {
            PlayerInfoInRoom pi = p == 1 ? Players[1] : Players[2];
            return pi.Resources.ContainsKey(resourceType) ? pi.Resources[resourceType] : 0;
        }

        // 获取指定圆形范围内满足条件的单位
        public Unit[] GetUnitsInCircleArea(Vec2 center, Fix64 r, Func<Unit, bool> filter)
        {
            return FC.Select(AllUnits,
                (u) => (filter == null || filter(u))
                    && (u.Pos - center).MaxAbsXOrY - u.cfg.RealSize <= r // fast check first
                    && (u.Pos - center).Length - u.cfg.RealSize <= r).ToArray();
        }

        // 获取指定矩形范围内满足条件的单位
        public Unit[] GetUnitInFanArea(Vec2 circleCenter, Fix64 circleR, Vec2 fanCenter, Fix64 fanR, Fix64 fanDir, Fix64 fanAngle, Func<Unit, bool> filter)
        {
            return FC.Select(AllUnits,
                (u) => (filter == null || filter(u)) 
                    && MU.IsFunOverlappedCircle(u.Pos, u.cfg.RealSize, fanCenter, fanR, fanDir, fanAngle)).ToArray();
        }

        public bool CheckSpareSpace(Vec2 center, Fix64 radius) { return CheckSpareSpace((int)center.x, (int)center.y, radius); }
        public bool CheckSpareSpace(Fix64 cx, Fix64 cy, Fix64 radius)
        {
            return Map.CheckSpareSpace(cx, cy, radius);
        }

        // 寻找离指定位置最近的一个可容纳指定半径单位的点，从离中心 fromDistance 的距离开始找
        public bool FindNearestSpareSpace(Vec2 center, Fix64 radius, Fix64 fromDistance, out Vec2 pt)
        {
            return Map.FindNearestSpareSpace(center, radius, fromDistance, out pt);
        }

        #endregion

        #region 房间内信息修改

        // 销毁房间内所有对象
        public virtual void Clear()
        {
            if (Map != null)
            {
                foreach (var u in AllUnits)
                {
                    smm.Del(u.UID);
                    u.Room = null;
                }

                Map.Clear();
            }

            if (BuffRunner != null)
                BuffRunner.Clear();

            if (TBRunner != null)
                TBRunner.Clear();

            UsrsID = null;
            Players = null;
            frameSeqNo = 0;
            rand = null;
            idSeq = 0;
        }

        // 设置资源数量
        void SetResource(int p, string resourceType, Fix64 num)
        {
            PlayerInfoInRoom pi = Players[p];
            pi.Resources[resourceType] = num;
        }

        // 增加指定资源
        protected virtual void NotifyResourceChanged(int p, string resourceType, Fix64 delta, Fix64 total) { }
        public Fix64 AddResource(int p, string rType, Fix64 delta)
        {
            // 检查一下上下限
            var r = GetResource(p, rType) + delta;
            var maxKey = rType + "_Max";
            var max = GetResource(p, maxKey);
            if (max > 0 && r > max)
                r = max;
            else if (r < 0)
                r = 0;

            SetResource(p, rType, r);
            NotifyResourceChanged(p, rType, delta, r);
            return r;
        }

        // 添加地图障碍
        protected virtual void OnObstacleAdded(Vec2[] vertices) { }
        public void AddObstacle(params Vec2[] vertices) { AddObstacle(new List<Vec2>(vertices)); }
        public void AddObstacle(IList<Vec2> vertices)
        {
            Map.AddObstacle(vertices);
            OnObstacleAdded(vertices.ToArray());
        }

        // 创建一个新的单位并放置在指定位置
        public Unit AddNewUnit(Unit building, string type, Vec2 pos, int player)
        {
            var u = UnitFactory.Instance.Create(type, NextUniqueID, UsrsInfo[player].GetCardLv(type));
            u.Player = player;
            u.Pos = pos;
            u.Dir = player == 2 ? 270 : 90;
            return AddUnitAt(building, u) ? u : null;
        }

        // 添加单位到指定位置
        protected virtual void OnUnitAdded(Unit building, Unit u) { }
        public bool AddUnitAt(Unit building, Unit u)
        {
            if (!Map.CheckSpareSpace(u.Pos, u.cfg.RealSize))
            {
                Vec2 newPt = Vec2.Zero;
                if (!Map.FindNearestSpareSpace(u.Pos, u.cfg.RealSize, 0, out newPt))
                    return false;

                u.Pos = newPt;
            }

            if (!Map.AddUnitAt(u))
                return false;

            if (!u.IsNeutral)
            {
                // 人口数也要调整
                var suppliment = u.cfg.Suppliment;
                AddResource(u.Player, "Suppliment", suppliment);
            }

            u.Room = this;
            var sm = u.CreateAI();
            if (sm != null)
                smm.Add(sm);

            BuffRunner.OnUnitAdded(u);
            OnUnitAdded(building, u);
            return true;
        }

        // 移除指定单位
        protected virtual void OnUnitRemoved(Unit u) { }
        Unit RemoveUnit(string uid)
        {
            smm.Del(uid);
            var u = GetUnit(uid);
            u.Room = null;
            Map.RemoveUnit(uid);

            if (u.cfg.IsAccessory)
                u.Owner.Accessories.Remove(u);

            // 人口数也要调整
            if (!u.IsNeutral)
            {
                var suppliment = u.cfg.Suppliment;
                AddResource(u.Player, "Suppliment", -suppliment);
            }

            BuffRunner.OnUnitRemoved(u);
            OnUnitRemoved(u);
            return u;
        }

        #endregion

        #region 建造相关

        // 开始建造建筑
        public Unit ConstructBuilding(string genType, Vec2 pos, int player)
        {
            var cfg = GetPlayerUnitConfig(player, genType);
            var cost = cfg.Cost;
            if (GetResource(player, "Money") < cost)
                return null;
            else if (cfg.Prerequisites != null && GetAllUnitsByPlayer(player,
                    (u) => u.BuildingCompleted && cfg.Prerequisites.FirstIndexOf(u.UnitType) >= 0).Length == 0)
                return null;
            else if (!CheckSpareSpace(pos, cfg.RealSize))
                return null;

            AddResource(player, "Money", -cost);
            var building = AddNewUnit(null, genType, pos, player);
            return building;
        }

        // 取消建造
        public void CancelBuilding(Unit u)
        {
            var cost = u.cfg.Cost;
            var refund = (int)(cost * 0.7f);
            u.Hp = 0;
            AddResource(u.Player, "Money", refund);
        }

        // 开始建造部队
        public bool ConstructBattleUnit(Unit building, string genType)
        {
            if (!building.cfg.GenUnitTypes.Contains(genType))
                return false;

            var player = building.Player;
            var cfg = GetPlayerUnitConfig(building.Player, genType);
            var cost = cfg.Cost;
            if (GetResource(player, "Money") < cost)
                return false;

            AddResource(player, "Money", -cost);

            building.UnitCosntructingWaitingList.Add(genType);
            NotifyConstructingWaitingListChanged(building, genType);
            return true;
        }

        // 通知部队单位进入建造列表
        public virtual void NotifyConstructingWaitingListChanged(Unit building, string genType) { }

        // 空投伞兵到指定位置，路径随机
        public bool CreateSoldierCarrier(int player, Vec2 dropPt)
        {
            var cfg = GetPlayerUnitConfig(player, "SoldierCarrier");
            var cost = cfg.Cost;
            if (GetResource(player, "Money") < cost)
                return false;
            else if (cfg.Prerequisites != null && GetAllUnitsByPlayer(player,
                    (u) => u.BuildingCompleted && cfg.Prerequisites.FirstIndexOf(u.UnitType) >= 0).Length == 0)
                return false;

            var us = GetUnitsByType("StarTrack", player);
            if (us == null || us.Length == 0)
                return false;

            var st = us[0];

            AddResource(player, "Money", -cost);

            var dir = dropPt - st.Pos;
            var mr = MapSize.x > MapSize.y ? MapSize.x : MapSize.y;
            dir = dir.Length > 1 ? dir / dir.Length : Vec2.Zero;
            var fromPt = st.Pos - dir * 100;
            var toPt = dropPt + dir * mr;
            var stc = AddNewUnit(null, "SoldierCarrier", fromPt, player);
            FC.For((int)stc.Power, (i) => { stc.UnitCosntructingWaitingList.Add("Soldier"); });
            stc.Dir = dir.Dir();
            stc.MovePath.Add(dropPt);
            stc.MovePath.Add(toPt);

            return true;
        }

        public bool FindAccessoryPos(Unit building, string genType, out Vec2 pos)
        {
            var cfg = GetPlayerUnitConfig(building.Player, genType);
            var maxAccNum = building.cfg.MaxAccessoryNum;
            bool found = false;

            // 找一个挂件位
            var p = Vec2.Zero;
            FC.For(maxAccNum, (i) =>
            {
                var arc = Fix64.Pi * 2 * i / maxAccNum;
                var r = building.cfg.RealSize + cfg.RealSize + Map.GridSizeScaler;
                p = building.Pos + new Vec2(r * Fix64.Cos(arc), r * Fix64.Sin(arc));
                found = GetUnitsInCircleArea(p, 10, (u) => u.cfg.IsAccessory && u.Owner == building).Length == 0;
            }, () => !found);

            pos = p;
            return found;
        }

        // 开始建造挂件
        public Unit ConstructAccessoryUnit(Unit building, string genType)
        {
            var maxAccNum = building.cfg.MaxAccessoryNum;
            var cfg = GetPlayerUnitConfig(building.Player, genType);
            var cost = cfg.Cost;
            var player = building.Player;
            var pos = Vec2.Zero;
            if (building.Accessories.Count >= maxAccNum)
                return null;
            if (GetResource(player, "Money") < cost)
                return null;
            else
            {
                // 同时只能有一个附件在建造
                foreach (var a in building.Accessories)
                {
                    if (!a.BuildingCompleted)
                        return null;
                }

                if (!FindAccessoryPos(building, genType, out pos))
                    return null;
            }

            AddResource(player, "Money", -cost);
            var acc = AddNewUnit(building, genType, pos, player);
            acc.Owner = building;
            building.Accessories.Add(acc);
            acc.Hp = 1; // 建筑初始 hp 只有 1，随着建造过程增加
            return acc;
        }

        // 通知生产资源
        public virtual void OnProduceResource(Unit u, string resType, Fix64 num) { }

        // 通知建筑建造完成
        public virtual void OnBuildingConstructingCompleted(Unit u) { u.Room.RefreshAI(u); }

        // 通知开始建造战斗单位
        public virtual void OnConstructingBattleUnitStarted(Unit building, string genType) { }

        // 通知单位建造完成
        public virtual Unit OnBattleUnitConstructingCompleted(Unit building, string genType)
        {
            // 检查人口上限
            var p = building.Player;
            var suppliment = GetPlayerUnitConfig(building.Player, genType).Suppliment;
            var curSuppliment = GetResource(p, "Suppliment");
            var supplimentLimit = GetResource(p, "SupplimentLimit");
            if (suppliment > 0 && suppliment + curSuppliment > supplimentLimit)
                return null;

            // 摆在建筑旁边
            var halfSize = GetPlayerUnitConfig(building.Player, genType).RealSize;
            var pos = building.Pos + (building.Player == 1 ? new Vec2(0, building.cfg.RealSize + 50) : new Vec2(0, -building.cfg.RealSize - 50));
            // if (!FindNearestSpareSpace(building.Pos, halfSize, building.cfg.RealSize, out pos))
            //    return null;

            return AddNewUnit(building, genType, pos, building.Player);
        }

        // 建筑升级
        public virtual void OnBuildingLevelUp(Unit u) { }
        public bool BuildingLevelUp(string uid, string toType)
        {
            var u = GetUnit(uid);
            if (u == null)
                return false;

            // 回收建筑，返一半钱
            if (toType == "Recycle")
            {
                AddResource(u.Player, "Money", u.cfg.Cost * 0.5f);
                RemoveUnit(uid);
                return true;
            }

            // 检查合法性
            var cfg = GetPlayerUnitConfig(u.Player, toType);
            if (!u.cfg.LevelUps.Contains(toType))
                return false;
            else if (cfg.Prerequisites != null && GetAllUnitsByPlayer(u.Player,
                (unit) => unit.BuildingCompleted && cfg.Prerequisites.FirstIndexOf(unit.UnitType) >= 0).Length == 0)
                return false;

            // 扣除费用
            var cost = cfg.Cost;
            if (GetResource(u.Player, "Money") < cost)
                return false;

            AddResource(u.Player, "Money", -cost);

            // 变更类型信息，并更新状态机
            u.UnitType = toType;
            u.BuildingCompleted = false;
            RefreshAI(u);

            OnBuildingLevelUp(u);

            return true;
        }

        #endregion

        #region 攻击相关

        // 执行攻击行为
        public virtual void DoAttack(Unit attacker, Unit target)
        {
            var AOEType = attacker.cfg.AOEAreaType;
            if (AOEType == "Fan")
                DoAOEAttackFan(attacker, target);
            else if (AOEType == "Pie")
                DoAOEAttackPie(attacker, target);
            else
                DoSingleAttack(attacker, target);
        }

        // 单体攻击
        public virtual void DoSingleAttack(Unit attacker, Unit target)
        {
            MakeDamage(attacker, new Unit[] { target });
        }

        // 圆形攻击，AOE 都不作用于挂件，因为挂件的血量和主体是相通的
        public virtual void DoAOEAttackPie(Unit attacker, Unit target)
        {
            var targets = new List<Unit>();
            targets.Add(target); // place main target as first one
            var ts = GetUnitsInCircleArea(target.Pos, attacker.cfg.AOEParams[0], (u) => attacker.CanAttack(u) && u.Hp > 0);
            foreach (Unit t in ts)
                if (!targets.Contains(t))
                    targets.Add(t);

            MakeDamage(attacker, targets.ToArray());
        }

        // 扇形攻击，AOE 都不作用于挂件，因为挂件的血量和主体是相通的
        public virtual void DoAOEAttackFan(Unit attacker, Unit target)
        {
            var targetPlayer = attacker.cfg.Power > 0 ? (attacker.Player == 1 ? 2 : 1) : attacker.Player;
            var dir = MU.v2Degree(target.Pos.x - attacker.Pos.x, target.Pos.y - attacker.Pos.y);
            var targets = new List<Unit>();
            targets.Add(target); // place main target as first one
            var ts = GetUnitInFanArea(target.Pos, target.cfg.RealSize,
                    attacker.Pos, attacker.cfg.AOEParams[0],
                    dir, attacker.cfg.AOEParams[1], (u) => attacker.CanAttack(u) && u.Hp > 0);
            foreach (Unit t in ts)
                if (!targets.Contains(t))
                    targets.Add(t);

            MakeDamage(attacker, targets.ToArray());
        }

        // 计算伤害
        public virtual void MakeDamage(Unit attacker, Unit[] targets)
        {
            foreach (var t in targets)
            {
                var d = Fix64.Zero;
                if (attacker.cfg.PowerType == "ByPrecentage")
                {
                    if (attacker.cfg.Power > 0) // hurt
                    {
                        d = t.cfg.MaxHp * attacker.Power / 100 - t.Defence;
                        d = d.Clamp(1, t.cfg.MaxHp);
                    }
                    else // heal
                    {
                        d = t.cfg.MaxHp * attacker.Power / 100;
                        d = d.Clamp(t.Hp - t.cfg.MaxHp, -1);
                    }
                }
                else // default is "ByValue"
                {
                    if (attacker.cfg.Power > 0) // hurt
                    {
                        d = attacker.Power - t.Defence;
                        d = d.Clamp(1, t.cfg.MaxHp);
                    }
                    else // heal
                    {
                        d = attacker.Power;
                        d = d.Clamp(t.Hp - t.cfg.MaxHp, -1);
                    }
                }

                t.OnDamage((int)d);
            }
        }

        #endregion

        #region 宝箱和 buff 相关

        // 局内 buff 管理
        public BuffRunner BuffRunner { get; protected set;}

        // 宝箱投放
        public TreasureBoxRunner TBRunner { get; protected set; }

        #endregion
    }
}
